﻿#include "videoeffect.h"
#include <QApplication>
#include <QBitmap>
#include <QDesktopWidget>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFileInfo>
#include <QFrame>
#include <QLabel>
#include <QLineEdit>
#include <QListView>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QScreen>
#include <QStringListModel>
#include <QTimer>
#include <QTimerEvent>
#include <QVBoxLayout>
#include <functional>
#include "globaltools.h"

VideoEffect::VideoEffect() : effFlag(NoEffect), pipFlag(NoPIP), capture(0),
#ifdef Q_OS_WIN
    wId(0),
#endif
    cursor_image(QLatin1String(":/gui/cursor-arrow.png"))
{

}

QPixmap &VideoEffect::captureImage()
{
    if(capturePixmap.isNull())
    {
        QPixmap black(1, 1);
        black.fill(Qt::black);
        capturePixmap.swap(black);
    }
    return capturePixmap;
}

void VideoEffect::clearCaptureImage()
{
    QMutexLocker locker(&mutex);
    if(capturePixmap.width() == 1 && capturePixmap.height() == 1)
        return;
    QPixmap black(1, 1);
    black.fill(Qt::black);
    capturePixmap.swap(black);
}

void VideoEffect::startCapture(const QSize &size)
{
#ifdef Q_OS_WIN
    endCaptureWindow();
#endif
    if(!capture)
    {
        capture = new QFrame(0, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
        capture->setAttribute(Qt::WA_DeleteOnClose);
        capture->setAttribute(Qt::WA_TransparentForMouseEvents);
        capture->setAttribute(Qt::WA_TranslucentBackground);
        capture->setFrameStyle(QFrame::Box | QFrame::Plain);
        capture->setLineWidth(2);
        auto p = capture->palette();
        p.setColor(capture->foregroundRole(), Qt::blue);
        capture->setPalette(p);

        connect(capture, SIGNAL(destroyed(QObject*)), SLOT(captureFrameDeleted()));
    }
    if(size != capture->contentsRect().size())
    {
        capture->resize(size.width() + capture->frameWidth() * 2, size.height() + capture->frameWidth() * 2);
        if(screen_capture_timer.isActive())
            screen_capture_timer.start(30, this);
        moveAndCapture();
    }
    if(capture->isHidden())
    {
        capture->show();
        QTimer::singleShot(0, this, SLOT(captureFrameShown()));
    }
}

#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <Psapi.h>
static BOOL CALLBACK lpEnumFunc(HWND hwnd, LPARAM lParam)
{
    if(!lParam)
        return FALSE;
    auto func = (std::function<BOOL(HWND)> *)lParam;
    return (*func)(hwnd);
}

static inline BOOL enumFunction(HWND hwnd, QMap<HWND,QString> &EnumWindowList, HWND currentWinId)
{
    WINDOWINFO wi = {0};
    wi.cbSize = sizeof(wi);
    if(GetWindowInfo(hwnd, &wi) && wi.dwStyle&WS_VISIBLE)
    {
        if(hwnd == currentWinId)
            return TRUE;
        TCHAR buf[128] = {0};
        int size = GetWindowText(hwnd,buf,128);
        QString wndName = QString::
    #ifdef UNICODE
        fromUtf16((const ushort *)
    #else
        fromLocal8Bit(
    #endif
        buf, size);
        if(wndName.isEmpty())
            return TRUE;
        DWORD pid = 0;
        QString baseName;
        GetWindowThreadProcessId(hwnd,&pid);
        if(!pid)
            return TRUE;
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, pid);
        if(!hProcess)
            return TRUE;
        if((size = GetProcessImageFileName(hProcess, buf, 128))>0)
        {
            QString image = QString::
            #ifdef UNICODE
                fromUtf16((const ushort *)
            #else
                fromLocal8Bit(
            #endif
                buf, size);
            if(!image.isEmpty())
                baseName = QFileInfo(image).fileName();
        }
        if(baseName.isEmpty() && (size = GetModuleBaseName(hProcess, 0, buf, 128))>0)
        {
            baseName = QString::
            #ifdef UNICODE
                fromUtf16((const ushort *)
            #else
                fromLocal8Bit(
            #endif
                buf, size);
        }
        if(baseName.isEmpty() && (size = GetModuleFileNameEx(hProcess, 0, buf, 128))>0)
        {
            QString module = QString::
            #ifdef UNICODE
                fromUtf16((const ushort *)
            #else
                fromLocal8Bit(
            #endif
                buf, size);
            if(!module.isEmpty())
                baseName = QFileInfo(module).fileName();
        }
        CloseHandle(hProcess);
        if(baseName.isEmpty())
            return TRUE;

        wndName.append("(pid: "+QString::number(pid));
        wndName.append(", " + QObject::trECSUTF8("文件名") + ": " + baseName);
        wndName.append(')');
        EnumWindowList[hwnd]=wndName;
    }
    return TRUE;
}

bool VideoEffect::chooseWid(QWidget* currentWidget)
{
    QMap<HWND,QString> EnumWindowList;
    WId currentWId = currentWidget? currentWidget->winId() : 0;
    std::function<BOOL(HWND)> func = [&EnumWindowList, currentWId](HWND hwnd){
        return enumFunction(hwnd, EnumWindowList, (HWND)currentWId);
    };
    bool success = false;
    if(EnumWindows(lpEnumFunc, (LPARAM)(&func)))
    {
        QDialog dlg(currentWidget, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
        dlg.resize(400,300);
        QVBoxLayout* verticalLayout = new QVBoxLayout(&dlg);
        QLabel* label = new QLabel(&dlg);
        label->setText(QObject::trECSUTF8("注意：此功能较占系统资源，请谨慎使用"));
        verticalLayout->addWidget(label);
        QLineEdit* editor = new QLineEdit(&dlg);
        editor->setPlaceholderText(QObject::trECSUTF8("筛选窗口"));
        verticalLayout->addWidget(editor);
        QListView* listView = new QListView(&dlg);
        listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
        QStringListModel* listModel = new QStringListModel(listView);
        QSortFilterProxyModel* filterModel = new QSortFilterProxyModel(listView);
        filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
        verticalLayout->addWidget(listView);
        QDialogButtonBox* buttonBox = new QDialogButtonBox(&dlg);
        buttonBox->setOrientation(Qt::Horizontal);
        buttonBox->setStandardButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
        verticalLayout->addWidget(buttonBox);
        connect(buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept()));
        connect(buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject()));
        listView->setSelectionMode(QAbstractItemView::SingleSelection);
        listView->setSelectionBehavior(QAbstractItemView::SelectRows);
        listModel->setStringList(EnumWindowList.values());
        filterModel->setSourceModel(listModel);
        listView->setModel(filterModel);
        connect(editor, SIGNAL(textChanged(QString)), filterModel, SLOT(setFilterWildcard(QString)));
        if(wId)
        {
            int i = EnumWindowList.keys().indexOf(wId);
            if(i >= 0)
            {
                QModelIndex index = filterModel->mapFromSource(listModel->index(i,0));
                if(index.isValid())
                    listView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
            }
        }
        connect(listView, SIGNAL(doubleClicked(QModelIndex)), &dlg, SLOT(accept()));
        dlg.setWindowTitle(QObject::trECSUTF8("选择窗口"));
        //选择窗口
        if(dlg.exec() == QDialog::Accepted)
        {
            QModelIndex index = filterModel->mapToSource(listView->currentIndex());
            if(index.isValid())
            {
                success = true;
                QMutexLocker locker(&mutex);
                effFlag = VideoEffect::WindowEffect;
                wId = EnumWindowList.keys().value(index.row());
                return true;
            }
        }
    }
    return false;
}

void VideoEffect::startCaptureWindow()
{
    endCapture();
    if(!window_capture_timer.isActive())
        window_capture_timer.start(30, this);
}

void VideoEffect::endCaptureWindow()
{
    window_capture_timer.stop();
}
#endif

void VideoEffect::endCapture()
{
    if(capture)
        capture->deleteLater();
    screen_capture_timer.stop();
}

void VideoEffect::endEffect()
{
    endCapture();
#ifdef Q_OS_WIN
    endCaptureWindow();
#endif
}

void VideoEffect::timerEvent(QTimerEvent *e)
{
    if(e->timerId() == screen_capture_timer.timerId())
    {
        moveAndCapture();
    }
#ifdef Q_OS_WIN
    else if(e->timerId() == window_capture_timer.timerId())
    {
        if(effFlag == WindowEffect)
        {
            WINDOWINFO wi = {0};
            wi.cbSize = sizeof(wi);
            if(wId && GetWindowInfo(wId, &wi) && wi.dwStyle&WS_VISIBLE)
            {
                QPixmap grabbed;
                QRect rt;
                if(QWidget* w = QWidget::find((WId)wId))
                {
                    rt = w->geometry();
                    grabbed = w->grab();
                }
                else
                {
                    rt = QRect(QPoint(wi.rcClient.left,wi.rcClient.top), QPoint(wi.rcClient.right,wi.rcClient.bottom));
                    grabbed = QApplication::primaryScreen()->grabWindow((WId)wId);
                }
                if(!grabbed.isNull())
                {
                    QPoint cursor = QCursor::pos();
                    if(rt.contains(cursor))
                    {
                        cursor -= rt.topLeft();
                        QPainter(&grabbed).drawPixmap(cursor, cursor_image);
                    }
                }
                else
                {
                    grabbed = QPixmap(1,1);
                    grabbed.fill(Qt::black);
                }
                QMutexLocker locker(&mutex);
                capturePixmap.swap(grabbed);
            }
            else
            {
                if(wId)
                {
                    log_debug()<<winGetLastError();
                }
                QPixmap pix(1,1);
                pix.fill(Qt::black);
                wId = Q_NULLPTR;
                window_capture_timer.stop();
                QMutexLocker locker(&mutex);
                capturePixmap.swap(pix);
            }
        }
        else
            window_capture_timer.stop();
    }
#endif
    QObject::timerEvent(e);
}

void VideoEffect::captureFrameShown()
{
    if(!screen_capture_timer.isActive())
    {
        moveAndCapture();
        screen_capture_timer.start(30, this);
    }
}

void VideoEffect::captureFrameDeleted()
{
    screen_capture_timer.stop();
    capture = 0;
    clearCaptureImage();
}

void VideoEffect::moveAndCapture()
{
    if(!capture || !capture->isVisible())
    {
#ifdef Q_OS_WIN
        window_capture_timer.stop();
#endif
        return;
    }
    QPoint cursor = QCursor::pos();
    QDesktopWidget *desktop = QApplication::desktop();
    int screenNumber = desktop->screenNumber(cursor);
    QRect desktopRect = desktop->screenGeometry(screenNumber);
    QRect windowGeometry = capture->geometry();
    QPoint oldTopLeft = windowGeometry.topLeft();
    windowGeometry.moveCenter(cursor);
    int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
    if(windowGeometry.left() < desktopRect.left())
        x1 = x2 = desktopRect.left() - windowGeometry.left();
    else if(windowGeometry.right() > desktopRect.right())
        x1 = x2 = desktopRect.right() - windowGeometry.right();
    if(windowGeometry.top() < desktopRect.top())
        y1 = y2 = desktopRect.top() - windowGeometry.top();
    else if(windowGeometry.bottom() > desktopRect.bottom())
        y1 = y2 = desktopRect.bottom() - windowGeometry.bottom();
    windowGeometry.adjust(x1, y1, x2, y2);
    if(oldTopLeft != windowGeometry.topLeft())
        capture->move(windowGeometry.topLeft());

    QRect content = capture->contentsRect();
    QPoint topLeft = capture->mapToGlobal(content.topLeft());
    QScreen *screen = QGuiApplication::screens().at(screenNumber);
    QPixmap grabbed = screen->grabWindow(desktop->winId(), topLeft.x(), topLeft.y(), content.width(), content.height());
    if(!grabbed.isNull())
    {
        cursor -= topLeft;
        QPainter(&grabbed).drawPixmap(cursor, cursor_image);
    }
    QMutexLocker locker(&mutex);
    capturePixmap.swap(grabbed);
}
